#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " "
#define LINE_SIZE 1024          //假定一行只能输入这么多字符
#define ARGC_SIZE 32            //在命令行内最多输入32个字串

#define NUNE -1
#define IN_RDIR      0
#define OUT_RDIR     1
#define APPEND_RDIR  2

int quit = 0;                             //用于交互函数
int lastcode = 0;                         //用于正常执行函数
extern char** environ;                    //传入环境变量
char commandline[LINE_SIZE];              //用于交互函数
char* argv[ARGC_SIZE];                    //用于字符串分割后续函数
char pwd[LINE_SIZE];
char* rdirfilename = NULL;                 //用于重定向
int rdir = NONE;

const char* getusername()
{
  return getenv("USER");
}
const char* gethostname()
{
  return getenv("HOSTNAME");
}
void getpwd()
{
  getcwd(pwd,sizeof(pwd));
}

void check_redir(char* cmd)
{
  char* pos = cmd;
  while(*pos)
  {
    if(*pos=='>')
    {
      if(*(pos+1)=='>')
      {
        *pos='\0';
        *pos='\0';
        while(isspace(*pos))
        {
          pos++;
        }
        rdirfilename=pos;
        rdir=APPEND_RDIR;
      }
      else 
      {
        *pos='\0';
        while(isspace(*pos))
        {
          pos++;
        }
        rdirfilename=pos;
        rdir=OUT_RDIR;

      }
    }
    else if(*pos=='<')
    {
      *pos = '\0';
      pos++;
      while(isspace(*pos))
      {
        pos++;
      }
      rdirfilename=pos;
      rdir=IN_RDIR;
      break;
    }
    else 
    {

    }
  }
}
void iteract(char *cline,int size)           //用于交互
{
  getpwd();
  printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);
  char *s = fgets(cline,size,stdin);            //接收键盘信息

  assert(s);           //若fgets没接收到信息则终止程序
  //由于assert只在debug起效果release就自动删除了，主要用于调试
  (void)s;            //有的编译器对于定义的变量而没有使用会报错，这句主要告诉编译器该变量使用过了
  cline[strlen(cline)-1]='\0';        //把接收回车的字符替换为\0结束字符串
}

int splitstring(char commandline[],char* argv[])
{
  int i = 0;
  
  argv[i++] = strtok(commandline,DELIM);          
  while(argv[i++] = strtok(NULL,DELIM));   //argv数组最后一个参数为NULL，条件为假自然就终止循环
  return i-1;
}

void NormalExcute(char* argv[])
{
  
   pid_t id=fork();
   if(id<0)
   {
     perror("fork");        //创建子进程失败
     return;
   }
  else if(id==0)            //创建子进程成功
  {
    if(rdir == IN_RDIR)
    {

    }
    //子进程执行命令，直接程序替换
    execvp(argv[0],argv);
    exit(56);                                 //若没有替换成功则执行该语句，退出码随便填的
  }
   else                                       //父进程
   {
      int status = 0;                         //获取退出码
      pid_t rid=waitpid(id,&status,0);        //等待
      if(rid==id)                             //等待成功，回收子进程
      {
        lastcode = WEXITSTATUS(status);       //接收子进程退出码
      }
   }
}

int buildCommand(char*_argv[],int _argc)
{
    //设置了回退路径
    if(_argc == 2 && strcmp(_argv[0],"cd")==0)                      //有2个有效字符串分别是cd和路径且输入的是cd
    {
      chdir(_argv[1]);                          //回退到上个路径
      getpwd();                                 //刷新pwd
      sprintf(getenv("PWD"),"%s",pwd);                              //将特定字符串按照格式化到指定的字符串  
      return 1;
    }

    //设置了颜色
    if(strcmp(argv[0],"ls")==0)                 //是否为ls指令
    {
      _argv[_argc++]="--color";
      _argv[_argc]=NULL;
    }
    return 0;
}
int main()
{

  while(!quit)
  {
    //2.交互问题
    iteract(commandline,sizeof(commandline));
    
    //3.字符串分割问题,解析命令行
    int argc = splitstring(commandline,argv);
    if(argc == 0)continue;

    // for(int i=0;argv[i];i++)printf("[%d]:%s\n",i,argv[i]);    //确定了键盘能接收到信息就可以不用给我们输出验证了
   
    //4.指令判断
    //内建命令：本质就是一个shell内部的一个函数
    int n =  buildCommand(argv,argc);
   
    //5.普通命令的执行
    if(!n)                            //如果是内建命令就给n返回1，否则是普通命令返回0
    {
      NormalExcute(argv);            //传入解析的argv
    } 
  }
  return 0;
}
